/*
 * msgameter.c
 * 
 * Copyright 2012 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */
#define MSGAMETER_VERSION 3.1.4
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <pthread.h>
#include <math.h>
#include <arpa/inet.h>

#include <jack/jack.h>
#include <jack/types.h>

#include <msgserv.h>

#define MSG_PRIORITY MSG_NOTICE+2
#define DEFAULT_DISPLAY_NUM 1 /* number of display that we are displayed on */
#define MY_NAME "msgameter"


#define PINFO(smt, ...) do { printf(smt, ## __VA_ARGS__ ); printf("\n"); } while(0)
#define PDEBUG(str, ...) PDEBUGL(1, str, ##__VA_ARGS__);
#define PDEBUGL(x, str, ...) do{ if ( debug >= x ) _INTERNAL_LOGIT(str, ##__VA_ARGS__); }while(0)
#define PERROR(str, ...) do { printf(MY_NAME ": (%s:%d): ", __FILE__, __LINE__); printf(str, ##__VA_ARGS__); printf("error %d:%s \n", errno, strerror(errno)); }while(0)
#define _INTERNAL_LOGIT(str, ...) do { printf( MY_NAME ": (%s:%d): ", __FILE__, __LINE__); printf(str, ##__VA_ARGS__); printf("\n"); }while(0)


#define algorithm(x) ( (pow(x,5) + pow(x, 2))/2 )
//#define algorithm(x) ( (pow(x, 5) + pow(x, 2))/2 )
//#define algotithm(x) ( 1 - ( ( 3*pow(1.03188675,x+2)*( 20.0f * log10f(x) - x + 15 ) ) / 100.0f ) )
		//( 1 - ( ( ( 20.0f * log10f(x) + (-1*x+5) )*3*pow(1.03188675, (x+2) ) + (30*pow(1.03188675, x+2) ) ) / 100.0f ) )

int debug = 0;
int lcd_number = DEFAULT_DISPLAY_NUM;
float env[2] = {0.5,0.5};
int jack_connected = 0;
jack_port_t *input_port[2];
jack_client_t *client;

int sockfd;
in_addr_t server_ip = { 16777343 }; // 127.0.0.1


float lev_decay = 0.0003f;
float peak_decay = 0.0004f;
int peak_wait_ms = 2000;
int bar_width = 200;

pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int new_data = 0;


static void safe_exit() 
{
	if (jack_connected)
		jack_client_close (client);
	usleep(10000);
	msglcd_disconnect(sockfd);
	//pthread_cond_broadcast(&cond);
	pthread_mutex_destroy(&cond_mutex);
	pthread_cond_destroy(&cond);
	exit(0);
}

#define fatal(format, ...)\
do {\
	PERROR(format, ##__VA_ARGS__);\
	safe_exit();\
} while(0)

static void sig_hup (int param) 
{
	PINFO("Signal received. Goind down");
	safe_exit();
}

static void parse_cmd(int argc, char **argv)
{
	int c=0;
	opterr=0;
	while ( (c = getopt(argc, argv, "hbmpgna:di:")) != -1 )
		switch (c) {
		case 'h':
			printf("msgameter: monite pcm sound level through msgsrv system.\n"
				"\t-h\tdisplay this help.\n"
				"\t-b\tbar width(length)\n"
				"\t-m\tbar decay per usec\n"
				"\t-p\tpeak decay per usec\n"
				"\t-n\tpeak wait in miliseconds\n"
				"\t-a\tnumber of lcd to use, starting with 0\n"
				"\t-d\tincrease debug\n"
				"\t-g\tdecrease debug\n"
				"\t-i<IP address>\t server ip address.\n");
			exit(0);
		case 'b':
			bar_width = atoi(optarg);
			break;
		case 'm':
			lev_decay = atof(optarg);
			break;
		case 'p':
			peak_decay = atof(optarg);
			printf("Peak decay setted to %f\n", peak_decay);
			break;
		case 'n':
			peak_wait_ms = atoi(optarg);
			break;
		case 'a':
			lcd_number = atoi(optarg);
			break;
		case 'd':
			debug++;
			break;
		case 'g':
			debug--;
			break;
		case 'i':
			server_ip=inet_addr(optarg);
			break;
	}
}

static void handle_msg_updat(const int sockfd, const char nr, const char pri, const char *mesg, const int len) 
{
	int i;
	i = msglcd_updat(sockfd, nr, pri, mesg, len);
	if ( i == 0 )
		return;
	fatal("could not update mesg. msglcd_updat returned: %d", i);
}

static int msgsrv_poll(int sockfd, int sec)
{
	fd_set fds;
	int i = 0;
	struct timeval timeout = { sec, 0 };
	FD_ZERO (&fds);
	FD_SET (sockfd, &fds);
	i = select (sockfd + 1, &fds, NULL, NULL, &timeout);
	if (i == -1 && errno != EINTR) {
		if(errno == EBADF) { /* socket disconected from the other side */
			/* server have could been closed without sending me SRV_CLOSE */
			return(-1);
		}
		fatal ("Moc server select() failed: %d ", i);
	}
	i = msglcd_handle_server(sockfd);
	switch ( i ) {
	case -5:
	case -1:
		fatal("Strange error occured");
	case -4:
		fatal("Server closed the connection.");
	case -6:
		fatal("server sended error messege");
	case -2:
		fatal("Server closed the connection without sending exit mesg.");
	case -3:
		fatal("my mesg has somth wrong ");
	}
	return(0);
}

static void przeslij_cm(unsigned char pattern, unsigned int number) 
{
	/* to do:
	 * buff[4] indicates lcd used. well, i need to find a way
	 * to automagically get which lcd im using :/
	 * *sometimes i love gcc* */
	 /* patter - complicated */
	char buff[] = { 
		[0] = 0x1b,
		[1] = '[',
		[2] = 'r',
		[3] = number + '0',
		[4] = lcd_number,
		[5 ... 12] = 0,
		};
	int i, j;
	int num;
	int first = 1;
	
	for(i=0; i<5; i++) {
		num = (pattern>>i)&1;
		if ( num ) {
			if ( first ) {
				for(j=0; j<8; j++) {
					switch(j) {
					case 0:
					case 2:
					case 5:
					case 7:
						buff[j+5] |= 1<<i;
					/*case 1:
					case 3:
					case 4:
					case 6:*/
					}
				}
				first = 0;
			} else {
				for(j=0; j<8; j++) {
					buff[j+5] |= 1<<i;
				}
			}
		}
	}
	
	if ( msglcd_send_to_lcd(sockfd, buff, sizeof(buff)) < 0 )  {
		fatal("msgsend_to_lcdconnection");
	}
}

static int jack_set_values(jack_nframes_t nframes, void *arg)
{
	unsigned int i;
	jack_default_audio_sample_t *in[2];
	
	//PDEBUGL(3, "jack_set_values start");
	
	pthread_mutex_lock(&cond_mutex);
	
	in[0] = (jack_default_audio_sample_t *) jack_port_get_buffer (input_port[0], nframes);
	in[1] = (jack_default_audio_sample_t *) jack_port_get_buffer (input_port[1], nframes);
	
	for (i = 0; i < nframes; i++) {
		const float s = fabs(in[0][i]);
		if (s > env[0]) {
			env[0] = s;
		}
	}
	for (i = 0; i < nframes; i++) {
		const float s = fabs(in[1][i]);
		if (s > env[1]) {
			env[1] = s;
		}
	}
	
	new_data=1;
	pthread_cond_signal(&cond);
	pthread_mutex_unlock(&cond_mutex);

	
	return 0;
}


/* callback for jack's error messages */
static void jack_error (const char *msg)
{
	PDEBUG("jack_error %s", msg);
}

static void jack_shutdown(void *arg)
{;
	pthread_mutex_lock(&cond_mutex);
	new_data = 0;
	jack_connected = 0;
	env[0] = 0;
	env[1] = 0;
	pthread_cond_signal(&cond);
	pthread_mutex_unlock(&cond_mutex);
}

static int jack_nawiaz_polaczenie()
{
	jack_status_t status;
	char client_name[256];
	
	snprintf(client_name, 256, MY_NAME"-%d", getpid());

	/* open a client connection to the JACK server */
	client = jack_client_open (client_name, JackNoStartServer, &status );
	if (client == NULL) {
		PDEBUG("jack_client_open() failed, status = 0x%2.0x\n", status);
		if (status & JackServerFailed) {
				PDEBUG("Unable to connect to JACK server\n");
		}
		return(-3);
	}
	if (status & JackServerStarted) {
		PDEBUG("JACK server started\n");
	}
	if (status & JackNameNotUnique) {
		//client_name = jack_get_client_name(client); , i dont care
		PDEBUGL(2, "unique name `%s' assigned\n", client_name);
	}

	jack_set_error_function (jack_error);

	/* tell the JACK server to call `process()' whenever there is work to be done. */
	jack_set_process_callback (client, jack_set_values, 0);

	/* tell the JACK server to call `jack_shutdown()' 
	 * if it ever shuts down, either entirely, or if it
	 * just decides to stop calling us. */
	jack_on_shutdown (client, jack_shutdown, 0);

	/* create two ports */
	input_port[0] = jack_port_register (client, "input0",
					JACK_DEFAULT_AUDIO_TYPE,
					JackPortIsInput, 0);
	input_port[1] = jack_port_register (client, "input1",
					JACK_DEFAULT_AUDIO_TYPE,
					JackPortIsInput, 0);
	
	if ((input_port[0] == NULL) || (input_port[1] == NULL)) {
		PDEBUG("no more JACK ports available\n");
		jack_client_close (client);
		return(-1);
	}

	if (jack_activate (client)) {
		PDEBUG("cannot activate client");
		jack_client_close (client);
		return(-2);
	}
	
	return(1);
}


static void jack_get_values(float *lev)
{
	pthread_mutex_lock(&cond_mutex);
	lev[0] = env[0];
	lev[1] = env[1];
	env[0] = 0;
	env[1] = 0;
	new_data=0;
	pthread_mutex_unlock(&cond_mutex);
}

static void robocizna(struct timeval time_new)
{
	static float lev_old[2] = {1.0f,1.0f};
	static unsigned int peak_age[2] = {0,0};
	static float peak[2] = {0,0};
	static struct timeval time_old = {0,0};
	static unsigned char cgram_old[2] = {0,0};
	
	float lev_new[2];
	unsigned int lev_pos=0, peak_pos=0;
	int i, j, k;
	
	const unsigned int ms = (1000*(time_new.tv_sec - time_old.tv_sec)) + ((time_new.tv_usec - time_old.tv_usec)/1000);
	const float max_lev_decay = lev_decay * ms;
	const float max_peak_decay = peak_decay * ms;

	
	/* here are new values obtained! lev_new is a pointer ;) */
	jack_get_values(lev_new);
	PDEBUGL(3, "jackd_get_values: %4f, %4f", lev_new[0], lev_new[1]);
	if ( lev_new[0] > 3000.0f || lev_new[1] > 3000.0f ) { // tehres a nasty bug in jack...
		jack_client_close(client);
		jack_connected = 0;
		return;
	}
	
	for (j=0; j<2; j++) {
		char buff[41] = { [0 ... 39] = ' ', [40] = '\0' };
		peak_age[j] += ms;
		/* calculate lev_new with max_decay */
		if (lev_old[j] > max_lev_decay && lev_new[j] < lev_old[j] - max_lev_decay)
			lev_new[j] = lev_old[j] - max_lev_decay;
		if (lev_old[j] < max_lev_decay && lev_new[j] > lev_old[j] + max_lev_decay)
			lev_new[j] = lev_old[j] + max_lev_decay;
		/* calculate peak position */
		if (lev_new[j] >= peak[j]) {
			peak[j] = lev_new[j];
			peak_age[j] = 0;
		}
		if (peak_age[j] >= peak_wait_ms) {
			if ( lev_new[j] >= peak[j] - max_peak_decay || peak[j] < max_peak_decay ) {
				peak[j] = lev_new[j];
				peak_age[j] = 0;
			} else {
				peak[j] = peak[j] - max_peak_decay;
			}
		}
		
		/* fill lev pos and peak_pos|algorithm makes it look "smooth" *its a macro* */
		lev_pos = algorithm(lev_new[j])*bar_width;
		peak_pos = algorithm(peak[j])*bar_width;
		/* check values */
		if ( lev_pos > bar_width ) lev_pos = bar_width;
		if ( peak_pos > bar_width ) peak_pos = bar_width;
		
		/* some information */
		PDEBUGL(2, "%d %d %.4f %.4f %u %u %f", ms, j, lev_old[j],  lev_new[j], lev_pos, peak_pos, peak[j]);
		PDEBUGL(2, "%u %u %f %f ", peak_age[j], ms, max_lev_decay, max_peak_decay);
		
		/* fill the buff array */
		for (i = 0; i < (lev_pos/5); i++)
			buff[i]=0xff;
		
		/* calculate cgram_new and send new cgram (this rocks) */
		if ( peak_pos/5 == lev_pos/5 ) {
			if (lev_pos % 5 == peak_pos % 5)  {
				if (lev_pos % 5) 
					buff[i++] = lev_pos%5-1;
			} else {
				int cgram_new = 0x00;
				for (k=0; k < lev_pos%5; k++)
					cgram_new |= 0x20 >> k;
					
				cgram_new |= 0x10 >> (peak_pos % 5);
				
				/* flush cgram_new only when its new, set buff[peak_pos/5] */
				if ( cgram_old[0] == cgram_new || cgram_old[1] == cgram_new ) {
					if (cgram_old[0] == cgram_new) {
						buff[peak_pos/5] = 0x05;
					} else {
						buff[peak_pos/5] = 0x06;
					}
				} else {
					przeslij_cm(cgram_new, 5+j);
					cgram_old[j] = cgram_new;
					buff[peak_pos/5] = 5 + j;
				}
			}
		} else {
			const int cgram_new = 0x10 >> (peak_pos % 5);
			
			if (lev_pos % 5) 
				buff[i++] = lev_pos%5-1;
			
			/* flush cgram_new only when new, set buff[peak_pos/5] */
			if ( cgram_old[0] == cgram_new || cgram_old[1] == cgram_new) {
				if (cgram_old[0] == cgram_new) {
					buff[peak_pos/5] = 0x05;
				} else {
					buff[peak_pos/5] = 0x06;
				}
			} else {
				przeslij_cm(cgram_new, 5+j);
				cgram_old[j] = cgram_new;
				buff[peak_pos/5] = 5 + j;
			}
		}
		if ( debug ) {
			printf("chan: %d buff: ", j);
			for (i = 0; i < 40; i++)
				printf("%c", buff[i] ? buff[i] : ' ');
			printf("|EOF\n");
		}
		/* update lcds */
		handle_msg_updat(sockfd, j, MSG_PRIORITY, buff, 40);
		lev_old[j] = lev_new[j];
	}
	time_old = time_new;
}

static void wait_usec_since(struct timeval ttime, int uwait) 
{
	struct timeval newtime;
	int sec, usec;
	gettimeofday(&newtime, NULL);
	
	sec = newtime.tv_sec - ttime.tv_sec;
	usec = newtime.tv_usec - ttime.tv_usec;
	
	if (sec == 0) {
		if (usec < uwait) { 
			usleep(uwait - usec);
		}
	} else if ( sec == 1 && uwait + usec > 0 ) {
		usleep (uwait + usec);
	}
}

int daemonize(void) 
{
	int PID;
	PID = fork();
	switch ( PID ) {
	case 0:
		fclose(stderr);
		fclose(stdin);
		fclose(stdout);
		break;
	case -1:
		break;
	default:
		exit(0);
	}
	return(PID);
}

int main(int argc, char **argv)
{
	struct timeval ttime={0,0};
	
	signal(SIGHUP,sig_hup);
	signal(SIGQUIT,sig_hup);
	signal(SIGTERM,sig_hup);
	signal(SIGINT,sig_hup);
	
	parse_cmd(argc,argv);

	if ( !server_ip ) {
		printf("pls give server ip to connect to \n");
		safe_exit();
	}	
	
	if ( debug < 0 ) {
		daemonize();
	}
	PDEBUG("Startup succed");
	
	sockfd = msglcd_connect(server_ip);
	if ( sockfd < 0) {
		fatal("Could not connect to lcd server %d \n", sockfd);
	}
	
	PINFO("Connected with lcdserver %s.\n", inet_ntoa(*(struct in_addr *)&server_ip));

	
	/* stopped only by signals */
	for (;;) {
		pthread_mutex_lock(&cond_mutex);
		jack_connected = jack_nawiaz_polaczenie();
		pthread_mutex_unlock(&cond_mutex);
		
		while(jack_connected > 0) {
			/* jackd refreshes in about 25 ms */
			
			/* check if there are new data */
			pthread_mutex_lock(&cond_mutex);
			while(jack_connected && !new_data)
				pthread_cond_wait(&cond, &cond_mutex);
			pthread_mutex_unlock(&cond_mutex);
			/* quarantie waiting for an amount of usecends (25ms) */
			wait_usec_since(ttime,25000);
			
			/* get new time */
			gettimeofday(&ttime, NULL);
			/* and now do it! (passing ttime reduces one gettimeofday call )*/
			robocizna(ttime);
		} /* for (;jack;) */
		
		//PDEBUGL(2, "jack nawiaz palaczenie %d ", jack_connected);
		handle_msg_updat(sockfd, 0, MSG_PRIORITY, " No connection to jack server   ", 29);
		handle_msg_updat(sockfd, 1, MSG_PRIORITY, " ", 1);
		if ( msgsrv_poll(sockfd, 30) < 0 ) /* handle server ok messeges */
			fatal("msgsrv poll");
		
		/* wait for 30 seconds and answer for pid request from server */
		if ( msgsrv_poll(sockfd, 30) < 0 )
			fatal("msgsrv poll");
	} /* for(;;) */
}

